import os.path
import re
from subprocess import CompletedProcess, run
from typing import List

from pygls.lsp import SignatureInformation, MarkupContent
from pygls.lsp.methods import COMPLETION, FORMATTING, HOVER, SIGNATURE_HELP
from pygls.lsp.types import (CompletionList, CompletionOptions,
                             CompletionParams, DocumentFormattingOptions,
                             DocumentFormattingParams, Hover, HoverOptions,
                             HoverParams, Optional, Position, Range,
                             SignatureHelp, SignatureHelpOptions,
                             SignatureHelpParams, TextEdit)
from pygls.server import LanguageServer
from pygls.workspace import Document

from utils import get_path_from_uri, read_builtin_cmds, get_builtin_entries, GetHelpDocError


class CMakeLanguageServer(LanguageServer):
    def __init__(self):
        super().__init__()
        cwd = os.path.dirname(os.path.abspath(__file__))
        self.builtin_cmds = read_builtin_cmds(os.path.join(cwd, 'builtin-cmds.json'))
        entries = get_builtin_entries()
        if entries is None:
            raise GetHelpDocError
        self.modules, self.policies, self.variables, self.properties = entries


ls = CMakeLanguageServer()


@ls.feature(FORMATTING, DocumentFormattingOptions())
def formatting(server: CMakeLanguageServer, params: DocumentFormattingParams) -> Optional[List[TextEdit]]:
    args = ['cmake-format', '--tab-size', str(params.options.tab_size), '--use-tabchars']
    use_tab = 'false' if params.options.insert_spaces else 'true'
    args.append(use_tab)
    native_path = get_path_from_uri(params.text_document.uri)
    args.append(native_path)
    res: CompletedProcess = run(
        args, capture_output=True, text=True, encoding='utf-8')

    if res.returncode:
        # TODO: return an error
        return None
    else:
        doc: Document = server.workspace.get_document(
            params.text_document.uri)
        # line and character are all zero-based integer
        start: Position = Position(line=0, character=0)
        last_line = doc.lines[len(doc.lines) - 1]
        end: Position = Position(
            line=len(doc.lines) - 1, character=len(last_line) - 1)
        return [TextEdit(range=Range(start=start, end=end),
                         new_text=res.stdout)]


@ls.feature(SIGNATURE_HELP, SignatureHelpOptions(trigger_characters=['('], retrigger_characters=[' ']))
def signature_help(server: CMakeLanguageServer, params: SignatureHelpParams) -> Optional[SignatureHelp]:
    doc: Document = server.workspace.get_document(params.text_document.uri)
    cur_line: str = doc.lines[params.position.line]
    before_cursor = cur_line[:params.position.character]
    found = re.findall(r'([A-Za-z_]+)', before_cursor)
    cmd = found[len(found) - 1]
    if cmd not in server.builtin_cmds:
        return None
    cmd_help: dict = server.builtin_cmds[cmd]
    sigs: List[SignatureInformation] = []
    for sig in cmd_help['sig']:
        sig_info = SignatureInformation(label=sig, documentation=cmd_help['doc'])
        sigs.append(sig_info)

    return SignatureHelp(signatures=sigs, active_signature=0, active_parameter=0)


@ls.thread()
@ls.feature(HOVER, HoverOptions())
def do_hover(server: CMakeLanguageServer, params: HoverParams) -> Optional[Hover]:
    doc: Document = server.workspace.get_document(params.text_document.uri)
    word: str = doc.word_at_position(params.position)
    line: str = doc.lines[params.position.line]
    # check if the word is a builtin commands
    if word in server.builtin_cmds:
        # signature should be wrapped in 'cmake' language to enable syntax highlight.
        sigs = '```cmdsignature\n' + '\n'.join(server.builtin_cmds[word]['sig']) + '\n```'
        cmd_help: str = server.builtin_cmds[word]['doc'] + '\n' + sigs
        markup_content = MarkupContent(kind='markdown', value=cmd_help)
        return Hover(contents=markup_content)

    module_arg: str = ''
    # if the word is a module
    if word in server.modules:
        if line.strip().startswith('include'):
            module_arg = '--help-module'
    elif word in server.policies:
        module_arg = '--help-policy'
    elif word in server.variables:
        module_arg = '--help-variable'
    elif word in server.properties:
        module_arg = '--help-property'

    if len(module_arg):
        doc_help = get_document(['cmake', module_arg, word])
        if doc_help is not None:
            return Hover(contents=doc_help)


@ls.feature(COMPLETION, CompletionOptions(trigger_characters=[',']))
def completions(server: CMakeLanguageServer, params: Optional[CompletionParams] = None) -> CompletionList:
    pass


def get_document(args: list[str]) -> Optional[str]:
    res: CompletedProcess = run(args, capture_output=True, text=True, encoding='utf-8')

    if res.returncode:
        # TODO: return an error
        return None
    return res.stdout


def main():
    ls.start_tcp('127.0.0.1', 2088)


if __name__ == '__main__':
    print('Hello CMake Language Server')
    main()
